// OpenGLView.cpp : implementation file
//

#include "stdafx.h"
#include "OpenGLView.h"
#include <afxstr.h>
#include <atlimage.h>
#include <Gdiplusimaging.h>
#include <PosGraph/Math/Angle.h>
#include ".\openglview.h"

const char* const COpenGLView::_ErrorStrings[] = {
	{"No Error"},					// 0
	{"Unable to get a DC"},			// 1
	{"ChoosePixelFormat failed"},	// 2
	{"SelectPixelFormat failed"},	// 3
	{"wglCreateContext failed"},	// 4
	{"wglMakeCurrent failed"},		// 5
	{"wglDeleteContext failed"},	// 6
	{"SwapBuffers failed"}			// 7
};


// COpenGLView

IMPLEMENT_DYNCREATE(COpenGLView, CView)

COpenGLView::COpenGLView() 
  : m_hRC(0), m_pDC(0), m_ErrorString(_ErrorStrings[0])
{
	m_bLMBPressed = false;
	m_bMMBPressed = false;
	m_bShowStockScene = true;
  m_bLastPointOK = false;
  //m_strDrawType = "Wireframe";

	// FPS
	m_fFPS = 0.0f;
}

COpenGLView::~COpenGLView()
{
}

BEGIN_MESSAGE_MAP(COpenGLView, CView)
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_ERASEBKGND()
	ON_WM_SIZE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	//ON_WM_MBUTTONUP()
	ON_WM_MOUSEMOVE()
//	ON_WM_CHAR()
  ON_WM_MOUSEWHEEL()
  ON_WM_RBUTTONUP()
  ON_WM_CHAR()
END_MESSAGE_MAP()


// COpenGLView drawing

void COpenGLView::OnDraw(CDC* pDC)
{
	makeCurrent();

	clock_t	ctLastTime, ctRazTime;
	
	ctLastTime = clock();
	GLDraw();
	ctRazTime = clock() - ctLastTime;

	m_fFPS = (float)CLOCKS_PER_SEC / ctRazTime;

	glGetIntegerv(GL_AUX_BUFFERS, &nAuxBuffers);
}


// COpenGLView diagnostics

#ifdef _DEBUG
void COpenGLView::AssertValid() const
{
	CView::AssertValid();
}

void COpenGLView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}
#endif //_DEBUG


// COpenGLView message handlers
int COpenGLView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;

	if (InitializeOpenGL() == false)
		return -1;

	return 0;
}

void COpenGLView::OnDestroy(void)
{
	CView::OnDestroy();
	
	if (FALSE == wglMakeCurrent(0, 0)) {
		SetError(2);
	}

	if (FALSE == wglDeleteContext(m_hRC)) {
		SetError(6);
	}

	if (m_pDC) {
		delete m_pDC;
	}
}

BOOL COpenGLView::OnEraseBkgnd(CDC* pDC)
{
	return TRUE;	// tell Windows not to erase the background
}

void COpenGLView::OnSize(UINT nType, int cx, int cy)
{
	CView::OnSize(nType, cx, cy);

  makeCurrent();

	if (0 >= cx || 0 >= cy){
		return;
	}

  m_nViewWidth = cx;
  m_nViewHeight = cy;
  m_ViewingParameters.setAspect((GLfloat) cx / (GLfloat) cy);

  updateProjectionMatrix();
  glViewport(0, 0, cx, cy);
}

void COpenGLView::makeCurrent()
{
  if (FALSE == wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC)) {	
		assert(0);
	}
}

void COpenGLView::OnLButtonDown(UINT nFlags, CPoint point)
{
  if (GetAsyncKeyState(VK_SHIFT) < 0)
  {
    m_ptPickWindow[0] = point;
  }
  else
  {
    m_ptLastPoint2D = point;
    m_bLastPointOK = mapToSphere(point, m_vLastPoint3D);
  }

	Invalidate(FALSE);
}

void COpenGLView::OnLButtonUp(UINT nFlags, CPoint point)
{
  m_bLastPointOK = false;

	if (nFlags == MK_SHIFT)
  {
    m_ptPickWindow[1] = point;
    GLPick();

    SendMessage(WM_PAINT);
	}	
}


BOOL COpenGLView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
  // TODO: Add your message handler code here and/or call default

  float d = - (float) zDelta / 120.0f *0.2f * m_fRadius;
  translate(PosGraph::Math::Vec3f(0.0f, 0.0f, d));
  SendMessage(WM_PAINT);

  return CView::OnMouseWheel(nFlags, zDelta, pt);
}


void COpenGLView::OnRButtonUp(UINT nFlags, CPoint point)
{
  if (nFlags & MK_SHIFT)
  {
    clearSelectedMeshPrimitives();
    SendMessage(WM_PAINT);
  }
}


void COpenGLView::OnMouseMove(UINT nFlags, CPoint point)
{
  if (nFlags & MK_SHIFT)
    return;

  CPoint  newPoint2D = point;

  if ((newPoint2D.x < 0) || (newPoint2D.x > width()) ||
      (newPoint2D.y < 0) || (newPoint2D.y > height()))
    return;

	// Left button: rotate around m_vCenter
	// Middle button: translate object
	// Left & middle button: zoom in/out

  float value_y;
  PosGraph::Math::Vec3f newPoint3D;
  bool  newPoint_hitSphere = mapToSphere(newPoint2D, newPoint3D);

	float dx = (float) (newPoint2D.x - m_ptLastPoint2D.x);
	float dy = (float) (newPoint2D.y - m_ptLastPoint2D.y);

	float w  = (float) width();
	float h  = (float) height();

	// enable GL context
	makeCurrent();

	// move in z direction
  if ((nFlags & MK_LBUTTON) && (nFlags & MK_MBUTTON))
  {
    //value_y = m_fRadius * dy * 30.0f / h;
    value_y = m_fRadius * dy * 3.0f / h;
    translate(PosGraph::Math::Vec3f(0.0, 0.0, value_y));
  }

	// move in x,y direction  
  else if (nFlags & MK_MBUTTON)
	{
    GLdouble  dModelviewMatrix[16];

    m_ViewingParameters.getModelviewMatrix(dModelviewMatrix);

		float z = - (float) ((dModelviewMatrix[ 2]*m_vCenter[0] + 
					dModelviewMatrix[ 6]*m_vCenter[1] + 
					dModelviewMatrix[10]*m_vCenter[2] + 
					dModelviewMatrix[14]) /
					(dModelviewMatrix[ 3]*m_vCenter[0] + 
					dModelviewMatrix[ 7]*m_vCenter[1] + 
					dModelviewMatrix[11]*m_vCenter[2] + 
					dModelviewMatrix[15]));

		float aspect     = w / h;
		float near_plane = 0.01f * m_fRadius;
    float top        = tanf((float) (m_ViewingParameters.getFovy()/2.0f*M_PI/180.0f)) * near_plane;
		float right      = aspect*top;

    translate(PosGraph::Math::Vec3f(2.0f*dx/w*right/near_plane*z, 
				-2.0f*dy/h*top/near_plane*z, 
					0.0f));
	}
	// rotate
  else if (nFlags & MK_LBUTTON)
	{
		if (m_bLastPointOK && GetAsyncKeyState(VK_MENU) < 0)
		{
			if (newPoint_hitSphere = mapToSphere(newPoint2D, newPoint3D))
			{
        PosGraph::Math::Vec3f axis = PosGraph::Math::cross(m_vLastPoint3D, newPoint3D);
        float cos_angle = PosGraph::Math::dot(m_vLastPoint3D, newPoint3D);
				if ( fabs(cos_angle) < 1.0 ) 
				{
					float angle = (float) (2.0f * acos(cos_angle) * 180.0f / M_PI);
					rotate( axis, angle );
				}
			}
  	}
	}

	// remember this point
	m_ptLastPoint2D = newPoint2D;
	m_vLastPoint3D = newPoint3D;
	m_bLastPointOK = newPoint_hitSphere;

	// trigger redraw
	PostMessage(WM_PAINT);
}



// Misc. functions

void COpenGLView::SetError(int e)
{
	// if there was no previous error,
	// then save this one
	if(_ErrorStrings[0] == m_ErrorString){
		m_ErrorString = _ErrorStrings[e];
	}
}


bool COpenGLView::InitializeOpenGL()
{
	m_pDC = new CClientDC(this);

	if(NULL == m_pDC){				// failure to get DC
		SetError(1);
		return false;
	}

	if(SetupPixelFormat() != true){
		return false;
	}

	if(0 == (m_hRC = wglCreateContext(m_pDC->GetSafeHdc()))){
		SetError(4);
		return false;
	}

  makeCurrent();

  glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glClearDepth(1.0f);							  // specify the back of the buffer as clear depth	
  glDisable(GL_DITHER);
	glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);

	// scene pos and size
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
  glGetDoublev(GL_MODELVIEW_MATRIX, m_ViewingParameters.modelviewMatrix());
  setScenePos(PosGraph::Math::Vec3f(0.0, 0.0, 0.0), 1.0);

	return true;
}

bool COpenGLView::SetupPixelFormat()
{
	static PIXELFORMATDESCRIPTOR pfd =	{
		sizeof(PIXELFORMATDESCRIPTOR),			// size of this pfd
			1,									// version number
			PFD_DRAW_TO_WINDOW |				// supprot window
			PFD_SUPPORT_OPENGL |				// supprot OpenGL
			PFD_DOUBLEBUFFER,					// double buffered
			PFD_TYPE_RGBA,						// RGBA type
			24,									// 24-bit color depth
			0, 0, 0, 0, 0, 0,					// color bits ignored
			0,									// no alpha buffer
			0,									// shift bit ignored
			0,									// no accumulation buffer
			0, 0, 0, 0,							// accum bits ignored
			16,									// 16-bit z-buffer
			0,									// no stencil buffer
			0,									// no auxiliary buffer
			PFD_MAIN_PLANE,						// main layer
			0,									// reserved
			0, 0, 0								// layer masks ignored
	};

	int pixelformat;

	if(0 == (pixelformat = ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd))){
		SetError(2);
		return false;
	}

	if(FALSE == SetPixelFormat(m_pDC->GetSafeHdc(), pixelformat, &pfd)){
		SetError(3);
		return false;
	}
	
	return true;
}

void COpenGLView::GLDraw()
{
  PreRenderScene();
	
  glMatrixMode(GL_PROJECTION);
  glLoadMatrixd(m_ViewingParameters.projectionMatrix()); 
  glMatrixMode(GL_MODELVIEW);
  glLoadMatrixd(m_ViewingParameters.modelviewMatrix());


	glPushMatrix();
	RenderScene();
	glPopMatrix();

	glFinish();

	if (FALSE == ::SwapBuffers(m_pDC->GetSafeHdc()))
  {
		SetError(7);
	}
}


void COpenGLView::PreRenderScene()
{
  glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void COpenGLView::RenderScene()
{
}

void COpenGLView::RenderStockScene()
{	
	int	i;
	GLfloat	x_max, x_min, z_max, z_min;
	GLsizei	x_grid_cnt, z_grid_cnt;

	x_max = z_max = 5.0f;
	x_min = z_min = -5.0f;
	x_grid_cnt = z_grid_cnt = 20;

  glDisable(GL_LIGHTING);
  glLineWidth(1.0f);  

	glBegin(GL_LINES);
	
	glColor3f(1.0f, 0.0f, 0.0f);		// x axis
	glVertex3f(1.0f, 0.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);
	
	glColor3f(0.0f, 1.0f, 0.0f);		// y axis
	glVertex3f(0.0f, 1.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);
	
	glColor3f(0.0f, 0.0f, 1.0f);		// z axis
	glVertex3f(0.0f, 0.0f, 1.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);

	//glColor3f(0.4f, 0.4f, 0.4f);
	glColor3f(0.0f, 0.0f, 0.0f);
	for(i = 0; i <= x_grid_cnt; i++) {		
		glVertex3f(x_min + i*(x_max - x_min)/x_grid_cnt, 0.0f, z_min);
		glVertex3f(x_min + i*(x_max - x_min)/x_grid_cnt, 0.0f, z_max);
	}

	for(i = 0; i <= z_grid_cnt; i++) {
		glVertex3f(x_min, 0.0f, z_min + i*(z_max - z_min)/z_grid_cnt);
		glVertex3f(x_max, 0.0f, z_min + i*(z_max - z_min)/z_grid_cnt);
	}
	
	glEnd();
}


void COpenGLView::setScenePos(const PosGraph::Math::Vec3f &vCenter, float fRadius)
{
  m_vCenter = vCenter;
  m_fRadius = fRadius;

  updateProjectionMatrix();
  viewAll();
}



void COpenGLView::updateProjectionMatrix()
{
  makeCurrent();  

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
  gluPerspective(m_ViewingParameters.getFovy(), m_ViewingParameters.getAspect(),
			0.01*m_fRadius, 100.0*m_fRadius);
  glGetDoublev(GL_PROJECTION_MATRIX, m_ViewingParameters.projectionMatrix());
	glMatrixMode(GL_MODELVIEW);

  
}



void COpenGLView::viewAll()
{
  double  dModelviewMatrix[16];
  m_ViewingParameters.getModelviewMatrix(dModelviewMatrix);

  translate( PosGraph::Math::Vec3f( 
    (float)
          -(dModelviewMatrix[0]*m_vCenter[0] + 
		      dModelviewMatrix[4]*m_vCenter[1] +
		      dModelviewMatrix[8]*m_vCenter[2] + 
		      dModelviewMatrix[12]),
    (float)
		    -(dModelviewMatrix[1]*m_vCenter[0] + 
		      dModelviewMatrix[5]*m_vCenter[1] +
		      dModelviewMatrix[9]*m_vCenter[2] + 
		      dModelviewMatrix[13]),
    (float)
		    -(dModelviewMatrix[2]*m_vCenter[0] + 
		      dModelviewMatrix[6]*m_vCenter[1] +
		      dModelviewMatrix[10]*m_vCenter[2] + 
		      dModelviewMatrix[14] +
		      3.0f*m_fRadius)));
  
}


void COpenGLView::translate(const PosGraph::Math::Vec3f &vTrans)
{
	// Translate the object by vTrans
	// Update m_dModelviewMatrix
	makeCurrent();
	glLoadIdentity();
	glTranslated(vTrans[0], vTrans[1], vTrans[2]);
  glMultMatrixd(m_ViewingParameters.modelviewMatrix());
  glGetDoublev(GL_MODELVIEW_MATRIX, m_ViewingParameters.modelviewMatrix());

  
}


void COpenGLView::rotate(const PosGraph::Math::Vec3f &vAxis, float fAngle)
{
  // Rotate around center m_vCenter, axis vAxis, by angle fAngle
  // Update m_dModelviewMatrix

  GLdouble  dModelviewMatrix[16];

  m_ViewingParameters.getModelviewMatrix(dModelviewMatrix);

  PosGraph::Math::Vec3f t(
    (float) (dModelviewMatrix[0]*m_vCenter[0] + 
		        dModelviewMatrix[4]*m_vCenter[1] +
		        dModelviewMatrix[8]*m_vCenter[2] + 
		        dModelviewMatrix[12]),
    (float) (dModelviewMatrix[1]*m_vCenter[0] + 
            dModelviewMatrix[5]*m_vCenter[1] +
            dModelviewMatrix[9]*m_vCenter[2] + 
            dModelviewMatrix[13]),
    (float) (dModelviewMatrix[2]*m_vCenter[0] + 
            dModelviewMatrix[6]*m_vCenter[1] +	
            dModelviewMatrix[10]*m_vCenter[2] + 
            dModelviewMatrix[14]));

	makeCurrent();
	glLoadIdentity();
	glTranslatef(t[0], t[1], t[2]);
	glRotated(fAngle, vAxis[0], vAxis[1], vAxis[2]);
	glTranslatef(-t[0], -t[1], -t[2]); 
  glMultMatrixd(m_ViewingParameters.modelviewMatrix());
	glGetDoublev(GL_MODELVIEW_MATRIX, m_ViewingParameters.modelviewMatrix());

  
}


bool COpenGLView::mapToSphere(const CPoint &pt2D, PosGraph::Math::Vec3f &v3D)
{
	if ( (pt2D.x >= 0) && (pt2D.x <= width()) &&
		(pt2D.y >= 0) && (pt2D.y <= height()) ) 
	{
		double x  = (double) (pt2D.x - 0.5*width())  / (double) width();
		double y  = (double) (0.5*height() - pt2D.y) / (double) height();
		double sinx         = sin(M_PI * x * 0.5);
		double siny         = sin(M_PI * y * 0.5);
		double sinx2siny2   = sinx * sinx + siny * siny;

		v3D[0] = (float) sinx;
		v3D[1] = (float) siny;
		v3D[2] = (float) (sinx2siny2 < 1.0 ? sqrt(1.0 - sinx2siny2) : 0.0);

		return true;
	}
	else return false;
}


#define BUFSIZE	1024
void COpenGLView::GLPick()
{
	GLuint	  uiSelectBuf[BUFSIZE];
	GLint	    nNumHits;
	GLint	    nViewport[4];
  GLdouble  dWidth, dHeight;
  CPoint    ptCenter;
  bool      bMultiSelect;

  makeCurrent();

	glGetIntegerv(GL_VIEWPORT, nViewport);

	glSelectBuffer(BUFSIZE, uiSelectBuf);
	glRenderMode(GL_SELECT);
	glInitNames();

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();

  dWidth  = std::abs(m_ptPickWindow[0].x - m_ptPickWindow[1].x);
  dHeight = std::abs(m_ptPickWindow[0].y - m_ptPickWindow[1].y);

  dWidth  = (dWidth < 5) ? 5 : dWidth;
  dHeight = (dHeight < 5) ? 5 : dHeight;
  ptCenter.x = (m_ptPickWindow[0].x + m_ptPickWindow[1].x) / 2;
  ptCenter.y = (m_ptPickWindow[0].y + m_ptPickWindow[1].y) / 2;

  gluPickMatrix(ptCenter.x, (nViewport[3] - ptCenter.y), dWidth, dHeight, nViewport);
  glMultMatrixd(projectionMatrix());
  GLSelectModeDraw();

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glFlush();

	nNumHits = glRenderMode(GL_RENDER);
  bMultiSelect = (dWidth > 5 || dHeight > 5) ? true : false;
  
  GLProcessHits(bMultiSelect, nNumHits, uiSelectBuf);
	Invalidate(FALSE);
}


void COpenGLView::GLProcessHits(bool bMultiSelect, GLint nNumHits, GLuint uiBuffer[])
{
	int	                    i;
	GLuint			            *ptr;
	float			              z[2];
  SelectedPrimitiveInfo   info;
 
	ptr = (GLuint *) uiBuffer;

  if (bMultiSelect)
  {
    std::cout << "multi-selection mode" << std::endl;
    std::cout << "# of hits: " << nNumHits << std::endl;

    for (i=0; i<nNumHits; ++i)
    {
      GLReadSelectedPrimitiveInfo(&ptr, z, info);
      selectMeshPrimitive(info);
      //info._pObject->select_primitive(info._ePrimitiveType, info._nIdx);
      
      debugPrintSelectedPrimitiveInfo(info);
    }
  }
  else if (nNumHits > 0)
  {
    std::cout << "single-selection mode" << std::endl;

    float                   min_z[2];
    SelectedPrimitiveInfo   min_info;

    for (i=0; i<nNumHits; ++i)
    {
      GLReadSelectedPrimitiveInfo(&ptr, z, info);      

      if (i==0 || min_z[0] > z[0])
      {
        min_z[0]  = z[0];
        min_z[1]  = z[1];
        min_info  = info;
      }
    }
    selectMeshPrimitive(min_info);
    //min_info._pObject->select_primitive(min_info._ePrimitiveType, min_info._nIdx);

    debugPrintSelectedPrimitiveInfo(min_info);
  }
}


void COpenGLView::GLReadSelectedPrimitiveInfo(GLuint **ptr, float z[2], SelectedPrimitiveInfo &info)
{
  GLuint			names;

  names = **ptr;                                                      (*ptr)++;
  z[0] = (float) **ptr/0x7fffffff;                                    (*ptr)++;
  z[1] = (float) **ptr/0x7fffffff;                                    (*ptr)++;
  info._pObject = (PosGraph::Modeling::Object *) **ptr;               (*ptr)++;
  info._ePrimitiveType = (PosGraph::Modeling::PrimitiveType) **ptr;   (*ptr)++;
  info._nIdx = **ptr;                                                 (*ptr)++;

  //std::cout << "number of names for this hit = " << names << std::endl;  
}

void COpenGLView::debugPrintSelectedPrimitiveInfo(const SelectedPrimitiveInfo &info)
{
  //std::cout << "[" << info._pObject->getFileName(). << ", ";  
  switch (info._ePrimitiveType)
  {
  case PosGraph::Modeling::kVertex:     std::cout << "v: ";       break;
  case PosGraph::Modeling::kEdge:       std::cout << "e: ";       break;
  case PosGraph::Modeling::kFace:       std::cout << "f: ";       break;
  case PosGraph::Modeling::kHalfedge:   std::cout << "he: ";      break;
  case PosGraph::Modeling::kHalfface:   std::cout << "hf: ";      break;
  case PosGraph::Modeling::kCell:       std::cout << "c: ";       break;
  case PosGraph::Modeling::kWedge:      std::cout << "w: ";       break;
  }
  std::cout << info._nIdx << "]" << std::endl;
  TRACE("v:%d\n", info._nIdx);
}

void COpenGLView::captureFrameBuffer(const std::string &strFilename)
{
  CImage  image;
  BYTE    rgb[3];

  image.Create(m_nViewWidth, m_nViewHeight, 24);
  GLubyte *buffer = new GLubyte[4 * m_nViewWidth * m_nViewHeight];

  wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC);
  glReadBuffer(GL_BACK);
  //glReadBuffer(GL_FRONT);
  glReadPixels(0, 0, m_nViewWidth, m_nViewHeight, GL_RGBA, GL_UNSIGNED_BYTE, buffer);


  for (int i=0; i<m_nViewWidth; i++)
  for (int j=0; j<m_nViewHeight; j++)
  {
    rgb[0] = buffer[4*(i + j*m_nViewWidth) + 0];
    rgb[1] = buffer[4*(i + j*m_nViewWidth) + 1];
    rgb[2] = buffer[4*(i + j*m_nViewWidth) + 2];

    image.SetPixelRGB(i, (m_nViewHeight-1) - j, rgb[0], rgb[1], rgb[2]);
  }

  image.Save((LPCTSTR) strFilename.c_str());

  delete[] buffer;
}

/*
void COpenGLView::capturePDF(const std::string &strFilename)
{
  FILE  *pFile = fopen(strFilename.c_str(), "wb");
  GLint buffsize = 0, state = GL2PS_OVERFLOW;
  GLint viewport[4];

  glGetIntegerv(GL_VIEWPORT, viewport);


  while (state == GL2PS_OVERFLOW)
  {
    buffsize += 1024*1024;
    gl2psBeginPage("MyTitle", "MySoftware", viewport,
      GL2PS_EPS, GL2PS_BSP_SORT, GL2PS_SILENT |
      GL2PS_SIMPLE_LINE_OFFSET | GL2PS_NO_BLENDING |
      GL2PS_OCCLUSION_CULL | GL2PS_BEST_ROOT,
      GL_RGBA, 0, NULL, 0, 0, 0, buffsize,
      pFile, strFilename.c_str());

    GLDraw();

    state = gl2psEndPage();
  }
  fclose(pFile);
}
*/
